﻿using System;
using System.Configuration;
using System.Configuration.Install;
using System.IO;
using System.Linq;
using System.Reflection;
using System.ServiceProcess;
using System.Threading;
using SBLogging;
using Timer = System.Timers.Timer;

namespace SBWindowsService
{
    /// <summary>
    /// Base class for windows service.
    /// </summary>
    public abstract class WindowsServiceBase : ServiceBase
    {
        /// <summary>
        /// Timer used to periodically execute service logic.
        /// </summary>
        private readonly Timer timer;

        /// <summary>
        /// Specifies whether importing is in progress.
        /// Int32 is used because of the Interlocked class.
        /// </summary>
        private int isWorking;

        /// <summary>
        /// Initializes a new instance of the <see cref="WindowsServiceBase"/> class.
        /// </summary>
        /// <param name="name">The service name.</param>
        protected WindowsServiceBase(string name)
        {
            if (String.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            timer = new Timer(PollInterval) { AutoReset = true };
            timer.Elapsed += (sender, args) => DoExecute();

            ServiceName = name;
            CanPauseAndContinue = false;
        }

        /// <summary>
        /// Generic purpose logger.
        /// </summary>
        protected ILogger Logger { get; set; }

        /// <summary>
        /// The poll interval between windows service execution.
        /// </summary>
        protected int PollInterval
        {
            get
            {
                return Int32.Parse(ConfigurationManager.AppSettings["PollInterval"] ?? "60") * 1000;
            }
        }

        /// <summary>
        /// Windows service executes job specified here.
        /// </summary>
        protected abstract void Execute();

        /// <summary>
        /// When implemented in a derived class, executes when a Start command is sent to the service by the Service Control Manager (SCM) or when the operating system starts (for a service that starts automatically). Specifies actions to take when the service starts.
        /// </summary>
        /// <param name="args">Data passed by the start command.</param>
        protected override void OnStart(string[] args)
        {
            if (Logger == null)
            {
                Logger = new NullLogger();
            }

            Logger.Information("Starting service " + ServiceName);

            base.OnStart(args);
            timer.Start();
        }

        /// <summary>
        /// When implemented in a derived class, executes when a Stop command is sent to the service by the Service Control Manager (SCM). Specifies actions to take when a service stops running.
        /// </summary>
        protected override void OnStop()
        {
            Logger.Information("Stopping service " + ServiceName);

            base.OnStop();
            timer.Stop();

            CleanUp();
        }

        /// <summary>
        /// Runs current Windows service as a console application while in DEBUG mode, otherwise as a normal Windows service.
        /// </summary>
        /// <typeparam name="TService">The type of the service.</typeparam>
        /// <param name="args">Service arguments.</param>
        protected static void Run<TService>(params string[] args) where TService : WindowsServiceBase, new()
        {
            var service = new TService();

            if (args != null && args.Length > 0)
            {
                for (int i = 0; i < args.Length; i++)
                {
                    args[i] = args[i].Trim().ToLowerInvariant();
                }

                var exeName = Path.GetFileName(Assembly.GetCallingAssembly().Location);
                if (args.Contains("uninstall"))
                {
                    using (var installer = new AssemblyInstaller(exeName, null) { UseNewContext = true })
                    {
                        installer.Uninstall(null);
                        return;
                    }
                }
                if (args.Contains("install"))
                {
                    using (var installer = new AssemblyInstaller(exeName, null) { UseNewContext = true })
                    {
                        installer.Install(null);
                        installer.Commit(null);
                        return;
                    }
                }
            }

            if (Environment.UserInteractive)
            {
                service.OnStart(args);

                Console.WriteLine("Service is running... Press Ctrl+C to exit.");

                while (true)
                {
                    // loop to execute service    
                }
            }
            else
            {
                Run(service);
            }
        }

        /// <summary>
        /// Cleans resources up after the service has been stopped.
        /// </summary>
        protected virtual void CleanUp()
        {
            timer.Dispose();
        }

        /// <summary>
        /// Executes windows service job if it is in a right state.
        /// </summary>
        private void DoExecute()
        {
            if (Interlocked.Exchange(ref isWorking, 1) == 0)
            {
                Logger.Information("Execution process started...");
                try
                {
                    Execute();
                }
                catch (Exception e)
                {
                    Logger.Error(e.Message, e);
                }
                Logger.Information("Execution process completed...");

                Interlocked.Exchange(ref isWorking, 0);
            }
        }
    }
}
